///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IO_CURI_H_
#define _BUOLA_IO_CURI_H_

#include <buola/buola.h>
#include <buola/io/curischeme.h>
#include <buola/app/ccmdline.h>
#include <map>

namespace buola { namespace io {

///\addtogroup io
///@{

///\name URI
//@{
    
/////////////////////////////////////////////////////////////////////////////

/// URI (uniform resource identifier)

/// Uniquely represents a resource which is accessible by the computer.
///
/// The scheme is stored as a pointer to some URI scheme class, while the rest
/// of the URI is stored textually.
///
/// The implementation tries to follow as closely as possible the guidelines
/// in RFC 3986 (http://www.ietf.org/rfc/rfc3986.txt)
///
/////////////////////////////////////////////////////////////////////////////

class CURI
{
    enum EFlags
    {
        FLAG_NONE=0,
        FLAG_EMPTY=(1<<0),          ///<the URI is empty
        FLAG_SIMPLEPATH=(1<<2),     ///<the URI is a simple unescaped path, absolute or relative
        FLAG_HASAUTHORITY=(1<<4),   ///<the URI has an authority, i.e. starts with a double slash
        FLAG_HASUSER=(1<<5),        ///<the URI contains a username
        FLAG_HASPASS=(1<<6),        ///<the URI contains a password
        FLAG_HASPORT=(1<<7),        ///<the URI contains a port
        FLAG_HASQUERY=(1<<8),       ///<the URI contains a query
        FLAG_HASFRAGMENT=(1<<9)    ///<the URI contains a fragment
    };
    DECLARE_FRIEND_ENUM_BITOPS(EFlags);

    typedef std::pair<std::size_t,std::size_t> TRange;

public:
///\name constructors
//@{
    ///default constructor

    ///Initializes an empty URI
    CURI()
        :   mScheme(nullptr)
        ,   mFlags(FLAG_EMPTY)
    {}

    ///copy constructor
    CURI(const CURI &pURI)
        :   mScheme(pURI.mScheme)
        ,   mText(pURI.mText)
        ,   mFlags(pURI.mFlags)
    {}

    ///move constructor
    CURI(CURI &&pURI)
        :   mScheme(pURI.mScheme)
        ,   mText(std::move(pURI.mText))
        ,   mFlags(pURI.mFlags)
    {}
//@}

private:
    CURI(CURIScheme *pScheme,const std::string &pText,EFlags pFlags)
        :   mScheme(pScheme)
        ,   mText(pText)
        ,   mFlags(pFlags)
    {}

public:
///\name general information about the URI
//@{
    ///returns true if the URI is empty (as when default-constructed)
    bool empty() const            {   return (mFlags&FLAG_EMPTY);         }

    ///returns true if the URI is relative

    ///Relative URIs do not have a scheme and must be resolved with respect to some absolute URI.
    ///
    ///This is not the same as a relative path. A relative filesystem path will not be a relative URI, since it has
    ///a scheme. Relative paths can be checked for with \ref RelativePath.
    bool IsRelativeURI() const      {   return !mScheme;      }

    ///converts the whole URI to a string, including the scheme
    std::string ToString() const;
    ///converts the whole URI to a wide string, including the scheme
    std::wstring ToWString() const;
//@}

private:
    TRange UserRange() const;
    TRange PassRange() const;
    TRange HostRange() const;
    TRange PortRange() const;
    TRange PathRange() const;
    TRange FolderRange() const;
    TRange NameRange() const;
    TRange ExtensionRange() const;
    TRange NameWithoutExtensionRange() const;
    TRange QueryRange() const;
    TRange FragmentRange() const;

    std::string UnescapeRange(const TRange &pRange) const;

public:
///\name query components of the URI
//@{
    ///returns the scheme
    CURIScheme *Scheme() const   {   return mScheme;                     }
    ///returns the scheme
    std::string SchemeName() const   {   return mScheme?mScheme->GetName():std::string();                     }
    ///returns the whole hierarchical part of the URI
    const std::string &HierarchicalPart()   {   return mText;   }
    ///returns the user name part of the URI
    std::string User() const;
    ///returns the password part of the URI
    std::string Pass() const;
    ///returns the host part of the URI
    std::string Host() const;
    ///returns the port part of the URI
    std::string Port() const;
    ///returns the full path part of the URI
    std::string Path() const
    {
        if(mFlags&FLAG_SIMPLEPATH)
            return mText;
        return UnescapeRange(PathRange());
    }
    ///returns the folder name part of the URI
    std::string Folder() const;
    ///returns the file name part of the URI
    std::string Name() const;
    ///returns the file extension of the URI
    std::string Extension() const;
    ///returns the file name part of the URI, without the extension
    std::string NameWithoutExtension() const;
    ///returns true if the path is relative
    bool RelativePath() const;
    ///returns the query part of the URI
    std::string Query() const;
    ///returns the fragment part of the URI
    std::string Fragment() const;

    ///returns a vector containing all path components of the URI
    void GetPathComponents(std::vector<std::string> &pDst) const;

    ///returns a map containing all query items (key-value pairs)
    std::map<std::string,std::string> GetQueryItems() const;
    ///returns true if a certain query key is present
    bool HasQueryItem(const std::string &pItem) const;
    ///returns the value corresponding to a query key
    std::string GetQueryItem(const std::string &pItem) const;
//@}
    
///\name set components of the URI
//@{
    ///sets the user name part of the URI
    void SetUser(const std::string &pUser);
    ///sets the password part of the URI
    void SetPass(const std::string &pPass);
    ///sets the host name part of the URI
    void SetHost(const std::string &pHost);
    ///sets the port part of the URI
    void SetPort(const std::string &pPort);
    ///sets the full path part of the URI
    void SetPath(const std::string &pPath);
    ///sets the folder name part of the URI
    void SetFolder(const std::string &pFolder);
    ///sets the file name part of the URI
    void SetName(const std::string &pName);
    ///sets the file extension part of the URI
    void SetExtension(const std::string &pExt);
    ///sets the file name part of the URI, without modifying the extension
    void SetNameWithoutExtension(const std::string &pName);
    ///sets the query part of the URI
    void SetQuery(const std::string &pUser);
    ///sets the fragment part of the URI
    void SetFragment(const std::string &pUser);

    ///appends a path component to the path part of the URI
    void AppendPathComponent(const std::string &pPath);

    ///appends or replaces the path part of the URI

    ///If \p pPath starts with a '/', it behaves like \ref SetPath. Otherwise, it behaves like \ref AppendPathComponent
    void AppendOrReplacePath(const std::string &pPath);

    ///adds a key-value pair to the query part of the URI
    void AddQueryItem(const std::string &pKey,const std::string &pValue);
//@}
//  void Correct();
    void Normalize();
    void Canonicalize();
    void MakeParent();
    void MakeAbsolute(const CURI &pBase);
    ///returns a new URI representing the folder that contains the current URI
    CURI GetParent() const;
    std::string GetRelativeTo(const CURI&) const;

///\name operators
//@{
    ///copy assignment operator
    CURI &operator=(const CURI &pRH)
    {
        mFlags=pRH.mFlags;
        mText=pRH.mText;
        mScheme=pRH.mScheme;
        return *this;
    }

    ///move assignment operator
    CURI &operator=(CURI &&pRH)
    {
        mFlags=pRH.mFlags;
        mText=std::move(pRH.mText);
        mScheme=pRH.mScheme;
        return *this;
    }

    ///comparison operator
    
    ///\todo: do a semantic comparison
    bool operator==(const CURI &pRH) const
    {
        return (mScheme==pRH.mScheme&&mText==pRH.mText);
    }

    ///\todo: do a semantic comparison
    bool operator!=(const CURI &pRH) const
    {
        return !operator==(pRH);
    }

    ///appends a path element to the URI
    CURI &operator/=(const std::string &pPath)
    {
        AppendPathComponent(pPath);
        return *this;
    }

    ///returns a new URI with a path element appended
    CURI operator/(const std::string &pPath) const
    {
        CURI lURI(*this);
        lURI.AppendPathComponent(pPath);
        return lURI;
    }

    ///appends text to the file name
    CURI &operator+=(const std::string &pPath)
    {
        SetName(Name()+pPath);
        return *this;
    }

    ///returns a new URI with a path element appended
    CURI operator+(const std::string &pPath) const
    {
        CURI lURI(*this);
        lURI.SetName(Name()+pPath);
        return lURI;
    }
//@}

private:
    CURIScheme *mScheme;
    std::string mText;
    EFlags mFlags;

    friend CURI uri(const std::string &pURI);
    friend CURI relative(const std::string &pURI);
    friend CURI file(const std::string &pURI);
    friend CURI file(const std::vector<std::string> &pComponents);
};

///returns the handler for the file: URI scheme
CURIScheme *file_scheme();

///constructs a URI from a URI string
CURI uri(const std::string &pURI);

///constructs a URI from a URI string
inline CURI uri(const std::wstring &pURI)  
{   
    return uri(utf8(pURI)); 
}

///constructs a relative URI
CURI relative(const std::string &pURI);

///constructs a URI in the file: scheme

///\param pPath can be an absolute or relative file path
inline CURI file(const std::string &pPath)
{
    return CURI(file_scheme(),pPath,CURI::FLAG_SIMPLEPATH);
}

///construct a URI from a command-line argument
CURI arg(int i,const CCmdLine &pCmdLine=cmd_line());

///constructs a URI in the file: scheme

///\param pComponents must be a list of path components to be put together to form an absolute path
inline CURI file(const std::vector<std::string> &pComponents)
{
    if(pComponents.empty())
        return CURI(file_scheme(),"/",CURI::FLAG_SIMPLEPATH);
    else
    {
        std::string lJoined;

        for(const auto &c : pComponents)
        {
            lJoined+="/";
            lJoined+=c;
        }
        
        return CURI(file_scheme(),lJoined,CURI::FLAG_SIMPLEPATH);
    }
}

CURI get_cwd();
CURI get_home();
CURI get_temp(const std::string &pPattern=std::string());

std::string uri_escape(const std::string &pSrc);

//@}

///@}

/*namespace io*/ }

///\addtogroup application
///@{

///\name Process information
//@{

///returns the executable path
const io::CURI &get_executable_path();

//@}

///@}

/*namespace buola*/ }

#endif
